First read the quick flow to get the overall steps. After that there is a concrete example (Phosphor) with a small Node.js script you can run to generate the manifest.
selection.json or a CSS file).font/woff2, font/woff,
or font/ttf and role file).#iconPack=<prefix> to
the note (prefix: alphanumeric, hyphen, underscore only).The first step is to analyze if the icon set being packed can be integrated into Trilium.
Trilium only supports font-based icon sets, with the following formats:
| Extension | MIME type | Description |
|---|---|---|
.woff2
|
font/woff2
|
Recommended due to great compression (low size). |
.woff
|
font/woff
|
Higher compatibility, but the font file is bigger. |
.ttf
|
font/ttf
|
Most common, but highest font size. |
Trilium does not support the following formats:
.eot fonts (legacy and proprietary).In this case, the font must be manually converted to one of the supported
formats (ideally .woff2).
The manifest is a JSON object with an icons map.
Each entry key is the CSS/class id you will use (Trilium uses the CSS class
when rendering). Value object:
Example minimal manifest:
{
"icons": {
"ph-acorn": {
"glyph": "\uea3f",
"terms": ["acorn", "nut"]
},
"ph-book": {
"glyph": "\uea40",
"terms": ["book", "read"]
}
}
}
Phosphor Icons provide a
selection.jsonthat includes properties.code (the
codepoint) and properties.name (the icon
name). The goal: convert that into Trilium's manifest.
Sample selection.json excerpt:
{
"icons": [
{
"icon": {
"paths": [ /* [...] */ ],
"grid": 0,
"attrs": [{}],
"isMulticolor": false,
"isMulticolor2": false,
"tags": ["acorn"]
},
"attrs": [{}],
"properties": {
"id": 0,
"order": 1513,
"name": "acorn",
"code": 60314,
"ligatures": "acorn",
"prevSize": 16
},
"setIdx": 0,
"setId": 0,
"iconIdx": 0
},
/* [...] */
]
}
A tiny Node.js script to produce the manifest (place selection.json in
the same directory and run with Node 20+):
import { join } from "node:path";
import { readFileSync, writeFileSync } from "node:fs";
function processIconPack(packName) {
const path = join(packName);
const selectionMeta = JSON.parse(readFileSync(join(path, "selection.json"), "utf-8"));
const icons = {};
for (const icon of selectionMeta.icons) {
let name = icon.properties.name;
if (name.endsWith(`-${packName}`)) {
name = name.split("-").slice(0, -1).join("-");
}
const id = `ph-${name}`;
icons[id] = {
glyph: `${String.fromCharCode(icon.properties.code)}`,
terms: [ name ]
};
}
writeFileSync("manifest.json", JSON.stringify(icons, null, 2), "utf8");
console.log("manifest.json created");
}
processIconPack("light");
What to do with the script:
selection.json and build-manifest.js in
a folder.manifest.json — open it,
verify contents, then copy into a Trilium Code note (language: JSON).Before an icon pack can be used, it needs to have a prefix defined. This prefix uniquely identifies the icon pack so that it can be used throughout the application.
To do so, Trilium makes use of the same format that was used for the internal
icon pack (Boxicons). For example, when an icon from Boxicons is set, it
looks like this: #iconClass="bx bxs-sushi".
In this case, the icon pack prefix is bx and
the icon class name is bxs-sushi.
In order for an icon pack to be recognized, the prefix must be specified
in the #iconPack label.
For our example with Phosphor Icons, we can use the ph prefix
since it also matches the prefix set in the original CSS. So in this case
it would be #iconPack=ph.
.woff2, .woff,
.ttf) format.
font/woff2).role appears as file,
otherwise the font will not be identified..woff2,
.woff, .ttf. As such, there's not
much reason to upload more than one font per icon pack.#iconPack=<prefix> (for
Phosphor example: #iconPack=ph).If the icon pack doesn't show up, look through the Backend (server) logs for clues.
ERROR: Icon pack is missing WOFF/WOFF2/TTF attachment: Boxicons v3 400 (dup) (XRzqDQ67fHEK).